package org.wendu.wdoa.common.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.wendu.wdoa.common.CurrentUser;
import org.wendu.wdoa.common.OaConstants;
import org.wendu.wdoa.common.Result;
import org.wendu.wdoa.common.config.dao.SecurityDao;
import org.wendu.wdoa.common.config.model.User;
import org.wendu.wdoa.common.token.TokenUser;
import org.wendu.wdoa.common.token.TokenUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
@Configuration
@MapperScan({
        "org.wendu.wdoa.common.config.dao",
        "org.wendu.wdoa.common.token.dao"
})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityDao securityDao;

    //配置不加密的密码编码器,方法名一定要命名为passwordEncoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.toString().equals(encodedPassword);
            }
        };
    }


    private UserDetails getUserByUserId(String userId) throws UsernameNotFoundException {
        /*
            如果登录时，userId为账号，
            如果认证时（校验token），userId为令牌，不查数据库，返回UserDetails中userId和密码都为为令牌
         */
        if(userId != null && userId.length()>20){//userId为令牌
            //创建当前用户，使之与认证信息匹配（特别是密码要匹配）
            return new CurrentUser(userId,null,userId);
        }

        User user = securityDao.findUserByUserId(userId);
        return new CurrentUser(user.getU_id(), user.getU_name(), user.getU_pwd());
    }

    //配置获取当前用户的业务对象
    @Bean //UserDetailsService的bean名称必须为userDetailsService（方法名称）
    public UserDetailsService userDetailsService() {
        return this::getUserByUserId;

//        return username -> {
//            return getUserByUserId(username);
//        };

//        return username -> {
//            User user = securityDao.findUserByUserId(username);
//            return new CurrentUser(user.getU_id(),user.getU_name(),user.getU_pwd());
//        };

//        return new UserDetailsService(){
//
//            @Override
//            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//                User user = securityDao.findUserByUserId(username);
//                return new CurrentUser(user.getU_id(),user.getU_name(),user.getU_pwd());
//            }
//        };

    }

    //Spring Security 核心安全配置均在这里写
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //配置无条件放行的资源和需要登录后访问的资源
        http.authorizeRequests()
//                .antMatchers("/assets/**").permitAll()//无条件放行
//                .antMatchers("/css/**").permitAll()
//                .antMatchers("/fonts/**").permitAll()
//                .antMatchers("/js/**").permitAll()
//                .antMatchers("/favicon.ico").permitAll()
//                .antMatchers("/toError").permitAll()
//                .antMatchers("/error.html").permitAll()
//                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated();//需要认证

        //用自定义的过滤器替换指定的过滤器
        http.addFilterAt(customizedUsernamePasswordAuthenticationFilter(),
                UsernamePasswordAuthenticationFilter.class);//替换指定的过滤器

        //将令牌校验过滤器放入过滤器链中
        http.addFilter(new TokenVerifyFilter(authenticationManager()));

        //登录配置
//        http.formLogin()
//                .loginPage("/login.html")//配置自定义的登录页
//                //配置Spring Security内部登录功能对应的认证请求路径，不配置，默认：/login
//                .loginProcessingUrl("/security/login")
//                .usernameParameter("u_id")//配置登录的参数名称
//                .passwordParameter("u_pwd")
//                //.successForwardUrl("/toHome")//登录成功了跳转到哪里
//                //.failureForwardUrl("/toError")//登失败了跳转到哪里
//
//                //自定义认证成功后怎么办，需要传入一个成功后处理器对象
//                .successHandler(this::onAuthenticationSuccess)
//
//                //自定义认证失败后怎么办，需要传入一个失败后处理器对象
//                .failureHandler(this::onAuthenticationFailure)
//                .permitAll();//放行登录相关路径

        //退出配置
        http.logout()
                .logoutUrl("/home/exit")//配置springsecurity退出功能的地址
                .logoutSuccessHandler(this::onLogoutSuccess)//配置退出成功后的处理逻辑，即配置一个处理器
                .permitAll();//放行退出相关路径

        //配置自定义异常处理：未认证异常（未登录而访问了需要登录后访问的地址）、非法访问异常（登录后，访问了无权限的地址）
        http.exceptionHandling()
                .authenticationEntryPoint(this::onNotAuthentication)//未认证异常处理
                .accessDeniedHandler(this::onAccessDenied);//非法访问异常

        //http.cors();//设置不阻止跨域请求 服务网关已经设置，此处不能在设置允许跨域
        http.csrf().disable();//禁用防csrf攻击
        http.headers().frameOptions().disable();//禁用阻止iframe请求访问

        //不使用Session追踪用户
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    }

    @Bean //配置一个CorsConfigurationSource对象，用于定义跨域策略（配置）
    //org.springframework.web.cors.CorsConfigurationSource
    public CorsConfigurationSource corsConfigurationSource() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");//允许任何来源的主机资源发送跨域请求
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.setAllowCredentials(true);//允许客户端发送认证凭证（含有sessionId的cookie）

        source.registerCorsConfiguration("/**", corsConfiguration);

        return source;

    }

    public CustomizedUsernamePasswordAuthenticationFilter customizedUsernamePasswordAuthenticationFilter() throws Exception {
        CustomizedUsernamePasswordAuthenticationFilter filter =
                new CustomizedUsernamePasswordAuthenticationFilter();
        filter.setFilterProcessesUrl("/login");
        filter.setAuthenticationManager(this.authenticationManagerBean());//设置认证管理器
        filter.setUsernameParameter("u_id");
        filter.setPasswordParameter("u_pwd");
        filter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        filter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        return filter;
    }


    private void print(HttpServletResponse response, Result result) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();//这是一个工具对象可以实现对象json字符串互转
        String jsonStr = objectMapper.writeValueAsString(result);
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter out = response.getWriter();//字符输出流，向浏览器输出
        out.print(jsonStr);
        out.flush();
        out.close();
    }

    private void onAuthenticationSuccess(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Authentication authentication)
            throws IOException, ServletException {

        CurrentUser currentUser = (CurrentUser)authentication.getPrincipal();
        TokenUser tokenUser = new TokenUser(currentUser.getUserId(),currentUser.getUserName());
        String token = TokenUtils.sign(tokenUser,currentUser.getPassword());

        print(response, Result.OK("登录成功！",token));

    }

    private void onAuthenticationFailure(HttpServletRequest request,
                                         HttpServletResponse response,
                                         AuthenticationException exception)
            throws IOException, ServletException {

        print(response, Result.err(Result.CODE_ERR_BUSINESS, "账号或密码错误"));
    }

    private void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        TokenUtils.removeToken(request.getHeader(OaConstants.HEADER_NAME_TOKEN));
        print(response, Result.OK());
    }

    private void onNotAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        log.debug("{}",authException);

        print(response, Result.err(Result.CODE_ERR_UNLOGINED, "请登录后访问！"));


    }

    public void onAccessDenied(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        print(response, Result.err(Result.CODE_ERR_BUSINESS, "权限不足！"));

    }
}
